#include "lom.hh"
#include "lom-value.hh"
#include "tests.hh"
#include "lom-value-factory.hh"
#include "lom-context.hh"
#include "lom-transaction-events.hh"
#include <iostream>


using namespace lom;
using namespace std;
using namespace licada;

class LomEventTest : public licada::TestBase, public ILomEventHandler
{
protected:
    int cc, ic, id, pe;
    THandle testHdl;
    LomInstanceManager instanceMgr;
    LomClassManager classMgr;
    LomEventSystem eventSys;
    LomValueFactory factory;
public:
    virtual TNameType getName() { return "LomEventTest"; }
    virtual void setup() { 
	cc = ic = id = pe = 0; 
	instanceMgr.reset();
	instanceMgr.setEventHandler(&eventSys);
	classMgr.reset();
	classMgr.setEventHandler(&eventSys);
	eventSys.reset();
	eventSys.addEventHandler( this );
	factory.setup( &instanceMgr );
    }
    //Test that the reset actually took effect.
    virtual void testPrecondition() {
	//Should always have the data type symbols
	TEST_EQ(0, instanceMgr.getHandleToRoots().size());
	TEST_EQ(0, classMgr.getClassMap().size());
	TEST_EQ(1, eventSys.getEventSystems().size());
    }
    
    //Create a class and an instance, and set a property on the instance
    virtual void doOperation() {
	LomClass cls("test_class", &factory);
	cls.addProperty( LomProperty("prop1", EDataTypeInt64) );
	cls.addProperty( LomProperty("prop2", EDataTypeDouble), LomDouble(20));
	classMgr.addClass(cls);
	LomInstance* test = new LomInstance(classMgr.getClass("test_class"), &factory, &instanceMgr );
	test->addRef();
	test->setEventHandler( &eventSys );
	testHdl = test->getHandle();
	test->set( "prop1", LomInt64(42) );
    }
    
    virtual void testPostcondition() {
	TEST_EQ(1, cc);
	TEST_EQ(1, ic);
	TEST_EQ(0, id);
	TEST_EQ(1, pe);
	LomInstance* test = dynamic_cast< LomInstance* >( instanceMgr.getRoot( testHdl ) );
	TEST_TRUE(test);
	TEST_EQ(20.0, test->get("prop2").getDouble());
	TEST_EQ(42, test->get("prop1").getInt64());
    }

    virtual void teardown() {
	eventSys.removeEventHandler( this );
	classMgr.reset();
	instanceMgr.reset();
    }

    //Simply forwards the calls to child systems
    virtual void event( TNameType, const TValueVector&) {}
    virtual void classCreated( const LomClass& ) { ++cc; }
    virtual void instanceCreated( THandle ) { ++ic; }
    virtual void instanceDestroyed( THandle ) { ++id; }
    virtual void propertyEvent( THandle , licada::uint32 
				, const LomValue& ) { ++pe; }
    virtual void rootObjectSet( TNameType, const LomValue& ){}
};

class LomInstanceReferenceTest : public LomEventTest
{
public:
    virtual TNameType getName() { return "LomInstanceReferenceTest"; }
    virtual void doOperation() {
	LomClass cls("test_class", &factory);
	cls.addProperty( LomProperty("prop1", EDataTypeHandle) );
	classMgr.addClass(cls);
	LomInstance* test = new LomInstance(classMgr.getClass("test_class"), &factory, &instanceMgr );
	test->addRef();
	test->setEventHandler( &eventSys );
	testHdl = test->getHandle();
	LomInstance* test2 = new LomInstance(classMgr.getClass("test_class"), &factory, &instanceMgr );
	test2->setEventHandler( &eventSys );
	LomHandle val(&instanceMgr);
	val.set( test2->getHandle() );
	test->set( "prop1", val );
    }
    
    virtual void testPostcondition() {
	TEST_EQ(1, cc);
	TEST_EQ(2, ic);
	TEST_EQ(0, id);
	TEST_EQ(1, pe);
	LomInstance* test = instanceMgr.getInstance(testHdl);
	THandle test2Hdl = test->get("prop1").getHandle();
	LomInstance* test2 = instanceMgr.getInstance(test2Hdl);
	TEST_NEQ( test, test2);
    }
};

class LomReserveHandleTest : public licada::TestBase
{
    THandle hdl1;
    THandle hdl2;
    LomEventSystem eventSys;
    LomInstanceManager instanceMgr;
    LomClassManager classMgr;
    LomValueFactory factory;
    
public:
    virtual TNameType getName() { return "LomReserveHandleTest"; }
    virtual void setup() {
	instanceMgr.reset();
	instanceMgr.setEventHandler(&eventSys);
	classMgr.reset();
	classMgr.setEventHandler(&eventSys);
	eventSys.reset();
	factory.setup( &instanceMgr );
	LomClass cls("test_class", &factory);
	cls.addProperty( LomProperty("prop1", EDataTypeHandle) );
	classMgr.addClass(cls);
	LomInstance* test = new LomInstance(classMgr.getClass("test_class")
					    , &factory, &instanceMgr );
	test->addRef();
	test->setEventHandler( &eventSys );
	hdl1 = test->getHandle();
	hdl2 = instanceMgr.reserveHandle();
	LomHandle val( &instanceMgr );
	val.set( hdl2 );
	test->setIdx( 0, val );
    }
    virtual void testPrecondition() {
	LomInstance* test2 = instanceMgr.getInstance( hdl2, false );
	TEST_EQ( NULL, test2 );
	instanceMgr.reference( hdl2 );
    }
    virtual void doOperation() {
	LomInstance* test2 = new LomInstance( classMgr.getClass( "test_class" )
					      , &factory, &instanceMgr, hdl2 );
    }
    virtual void testPostcondition() {
	LomInstance* test2 = instanceMgr.getInstance( hdl2, true );
	uint32 refCount = test2->getRefCount();
	TEST_EQ( 2, refCount );
    }
    virtual void teardown() {
	instanceMgr.reset();
	classMgr.reset();
    }
};

class LomContextTest : public licada::TestBase, public ILomEventHandler
{
    LomContext context;
    unsigned ros;
    unsigned ic;
    unsigned id;
    
public:
    virtual TNameType getName() { return "LomContextTest"; }
    virtual void event( licada::TNameType, const TValueVector& ) {}
    virtual void classCreated( const LomClass& ) {}
    virtual void instanceCreated( THandle ) { ++ic; }
    virtual void instanceDestroyed( THandle ) { ++id; }
    virtual void propertyEvent( THandle 
				, licada::uint32
				, const LomValue&  ){}
    virtual void rootObjectSet( TNameType , const LomValue&  ) { ++ros; }
    virtual void setup() {
	context.setup();
	ros = 0;
	ic = 0;
	id = 0;
	LomClass cls( "test_class", context.getValueFactory() );
	cls.addProperty( LomProperty("prop1", EDataTypeHandle) );
	context.getClassManager()->addClass(cls);
	context.getLomEventSystem()->addEventHandler( this );
    }
    virtual void testPrecondition() { 
	TEST_EQ( 0, ros ); 
	TEST_EQ( 0, ic );
	TEST_EQ( 0, id );
	TEST_EQ( NULL, context.getRootObject( "testObj" ) );
    }
    virtual void doOperation() {
	ILomInstanceFactory* instFac = context.getInstanceFactory();
	
	THandle handle = instFac->createInstance( context.getClassManager()->getClass( "test_class" ) );
	context.setRootObject( "testObj", LomHandle( handle, NULL ) );
    }
    virtual void testPostcondition() {
	TEST_EQ( 1, ros );
	TEST_EQ( 1, ic );
	TEST_EQ( 0, id );
	//Ensure we can get the instance
	LomInstance* inst = context.getInstanceManager()->getInstance(
	    context.getRootObject( "testObj" )->getHandle() );
    }
    //Unlike the other tests, this method can be blank
    //because the context takes care of tearing down
    //objects in the appropriate order to avoid nasty
    //errors.
    virtual void teardown() {
    }
};

class LomTransactionTest : public ILomTransactionHandler, public TestBase
{
    LomContext context;
    uint32 ec; //event count
public:
    virtual TNameType getName() { return "LomTransactionTest"; }
    virtual void setup() {
	ec = 0;
	context.setup();
	context.getTransactionSystem()->addHandler( this );
    }
    virtual void testPrecondition() {
	TEST_EQ( ec, 0 );
    }
    virtual void doOperation() {
	context.getTransactionSystem()->beginTransaction( NULL );
	
	ILomInstanceManager& instanceMgr = *context.getInstanceManager();
	LomClassManager& classMgr = *context.getClassManager();
	
	LomClass cls("test_class", context.getValueFactory());
	cls.addProperty( LomProperty("prop0", EDataTypeHandle) );
	cls.addProperty( LomProperty("prop1", EDataTypeInt64) );
	cls.addProperty( LomProperty("prop2", EDataTypeDouble), LomDouble(20));
	classMgr.addClass(cls);
	ILomInstanceFactory* instFac = context.getInstanceFactory();
	THandle handle = instFac->createInstance( context.getClassManager()->getClass( "test_class" ) );
	context.setRootObject( "testObj", LomHandle( handle, NULL ) );
	LomInstance* test = context.getInstanceManager()->getInstance( handle );
	test->set( "prop1", LomInt64(42) );
	THandle hdl2 = instanceMgr.reserveHandle();
	LomHandle val( &instanceMgr );
	val.set( hdl2 );
	test->setIdx( 0, val );
	LomInstance* test2 = instanceMgr.getInstance( hdl2, false );
	TEST_EQ( NULL, test2 );
	instanceMgr.reference( hdl2 );
	context.getInstanceFactory()->createInstance( classMgr.getClass( "test_class" ), hdl2 );
	context.getTransactionSystem()->endTransaction();
    }
    virtual void testPostcondition() { 
	TEST_EQ( ec, 6 );
    }
    virtual void beginTransactionEvents() { ec = 0; }

    virtual void handle( const ILomTransactionEvent* evt ) {
	switch( ec )
	{
	case 0:
	    TEST_TRUE( dynamic_cast<const LomTransactionClassCreatedEvent*>(evt));
	    break;
	case 1:
	    TEST_TRUE( dynamic_cast<const LomTransactionInstanceCreatedEvent*>(evt));
	    break;
	case 2:
	    TEST_TRUE( dynamic_cast<const LomTransactionRootObjectEvent*>(evt));
	    break;
	case 3:
	    TEST_TRUE( dynamic_cast<const LomTransactionPropertyEvent*>(evt));
	    break;
	case 4:
	    TEST_TRUE( dynamic_cast<const LomTransactionPropertyEvent*>(evt));
	    break;
	case 5:
	    TEST_TRUE( dynamic_cast<const LomTransactionInstanceCreatedEvent*>(evt));
	    break;
	}
	++ec;
    }
    virtual void endTransactionEvents() {}
};



int main( int , char** )
{
    runTest<LomEventTest>();
    runTest<LomInstanceReferenceTest>();
    runTest<LomReserveHandleTest>();
    runTest<LomContextTest>();
    runTest<LomTransactionTest>();
    return 0;
}
